package org.apache.lenya.modules.xmldiff.usecases;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

import org.apache.lenya.cms.repository.History;
import org.apache.lenya.cms.repository.Revision;
import org.apache.lenya.cms.site.usecases.SiteUsecase;
import org.apache.lenya.cms.usecase.UsecaseException;
import org.apache.lenya.modules.xmldiff.util.ArrayUtil;
import org.outerj.daisy.diff.DaisyDiff;
import org.outerj.daisy.diff.HtmlCleaner;
import org.outerj.daisy.diff.html.HTMLDiffer;
import org.outerj.daisy.diff.html.SimpleDiffOutput;
import org.outerj.daisy.diff.html.TextNodeComparator;
import org.outerj.daisy.diff.html.dom.BodyNode;
import org.outerj.daisy.diff.html.dom.DomTreeBuilder;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

public class DiffUsecase extends SiteUsecase {

	protected static final String NEWER_REVISION_PARAM = "newer_revision_num";
	protected static final String ELDER_REVISION_PARAM = "elder_revison_num";
	protected static final String PARAM_DIFFERENCES = "differences";

	private Revision elderRevision;
	private Revision newerRevision;

	protected void prepareView() throws Exception {
		super.prepareView();
		prepareRevisions();
		if (!hasErrors()) {
			prepareHTMLView(elderRevision.getInputStream(), newerRevision
					.getInputStream());
			prepareXMLView(elderRevision.getInputStream(), newerRevision
					.getInputStream());
		}
	}

	public String getParameterAsString(String name, String defaultValue) {
		String parameter = super.getParameterAsString(name, defaultValue);
		if ("".equals(parameter)) {
			parameter = defaultValue;
		}
		return parameter;
	}

	protected void prepareRevisions() throws Exception {
		History history = getSourceDocument().getRepositoryNode().getHistory();
		int[] revNumbers = history.getRevisionNumbers();
		String nrevn = getParameterAsString(NEWER_REVISION_PARAM, "-1");
		String erevn = getParameterAsString(ELDER_REVISION_PARAM, "-1");
		int newerRevisionNum = Integer.valueOf(nrevn).intValue();
		int elderRevisionNum = Integer.valueOf(erevn).intValue();
		if (newerRevisionNum == -1) {
			addErrorMessage("Missing parameter [" + newerRevisionNum + "]");
			return;
		}
		revNumbers = ArrayUtil.unique(revNumbers);
		int newerRevIdx = ArrayUtil
				.findInUnsorted(revNumbers, newerRevisionNum);
		if (newerRevIdx == -1) {
			addErrorMessage("Revision [" + newerRevisionNum
					+ "] does not exist");
			return;
		}
		if (elderRevisionNum == -1) {
			// try get one
			if (newerRevIdx == revNumbers.length - 1) { // if revision is the
														// oldest
				addErrorMessage("Revision [" + newerRevisionNum
						+ "] is the oldest.");
				return;
			}
			elderRevisionNum = revNumbers[newerRevIdx + 1];
		} else {
			// check if exists
			int elderRevIdx = ArrayUtil.findInUnsorted(revNumbers,
					elderRevisionNum);
			if (elderRevIdx == -1) {
				addErrorMessage("Revision [" + newerRevisionNum
						+ "] does not exist");
				return;
			}
		}
		setElderRevision(history.getRevision(elderRevisionNum));
		setNewerRevision(history.getRevision(newerRevisionNum));
	}

	protected Revision getElderRevision() {
		return elderRevision;
	}

	protected void setElderRevision(Revision elderRevision) {
		this.elderRevision = elderRevision;
	}

	protected Revision getNewerRevision() {
		return newerRevision;
	}

	protected void setNewerRevision(Revision newerRevision) {
		this.newerRevision = newerRevision;
	}

	protected void prepareHTMLView(InputStream older, InputStream newer)
			throws Exception {
		InputSource oldSource = new InputSource(older);
		InputSource newSource = new InputSource(newer);

		HtmlCleaner cleaner = new HtmlCleaner();
		Locale locale = Locale.getDefault();

		String prefix = "diff";

		DomTreeBuilder oldHandler;
		DomTreeBuilder newHandler;
		try {
			oldHandler = new DomTreeBuilder();
			cleaner.cleanAndParse(oldSource, oldHandler);
			newHandler = new DomTreeBuilder();
			cleaner.cleanAndParse(newSource, newHandler);
		} catch (Exception e) {
			throw new UsecaseException(
					"Error while parsing document for diffing: ", e);
		}
		TextNodeComparator leftComparator = new TextNodeComparator(oldHandler,
				locale);
		TextNodeComparator rightComparator = new TextNodeComparator(newHandler,
				locale);

		SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory
				.newInstance();
		TransformerHandler result = tf.newTransformerHandler();

		ByteArrayOutputStream out = new ByteArrayOutputStream();

		Result domResult = new StreamResult(out);
		result.setResult(domResult);

		ContentHandler htmlDiff = result;
		// Document content filtering
		// XslFilter filter = new XslFilter();
		// ContentHandler postProcess = filter.xsl(result, "htmldiff.xsl");
		try {
			startDiffDocument(htmlDiff);
			SimpleDiffOutput output = new SimpleDiffOutput(htmlDiff, prefix);
			BodyNode diffNode = HTMLDiffer
					.diff(leftComparator, rightComparator);
			output.toHTML(diffNode);
			finishDiffDocument(htmlDiff);
			setParameter("html-output", DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().parse(
							new ByteArrayInputStream(out.toByteArray()))
			);
		} catch (Exception e) {
			throw new UsecaseException(
					"Failed translating diff document to xml: ", e);
		}

	}

	protected void prepareXMLView(InputStream older, InputStream newer)
			throws UsecaseException {
		SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory
				.newInstance();

		TransformerHandler result;
		try {
			result = tf.newTransformerHandler();
		} catch (TransformerConfigurationException e) {
			throw new UsecaseException("Failed to create handler for diff", e);
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();

		Result domResult = new StreamResult(out);
		result.setResult(domResult);

		ContentHandler xmlDiff = result;

		// Document content filtering
		// XslFilter filter = new XslFilter();
		// ContentHandler postProcess = filter.xsl(result, "tagdiff.xsl");

		try {
			startDiffDocument(xmlDiff);
			DaisyDiff.diffTag(new BufferedReader(new InputStreamReader(older)),
					new BufferedReader(new InputStreamReader(newer)), xmlDiff);
			finishDiffDocument(xmlDiff);
		} catch (Exception e) {
			throw new UsecaseException("Failed to create diff report: ", e);
		}
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			setParameter("xml-output", builder.parse(new ByteArrayInputStream(
					out.toByteArray())));
		} catch (Exception e) {
			throw new UsecaseException(
					"Failed translating diff document to xml: ", e);							
		}

	}

	private static void startDiffDocument(ContentHandler document)
			throws SAXException {
		document.startDocument();
		document.startElement("", "diffreport", "diffreport",
				new AttributesImpl());
		document.startElement("", "diff", "diff", new AttributesImpl());
	}

	private static void finishDiffDocument(ContentHandler document)
			throws SAXException {
		document.endElement("", "diff", "diff");
		document.endElement("", "diffreport", "diffreport");
		document.endDocument();
	}

}
